package com.wedimob.scrapper.network;

import java.net.URI;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import com.wedimob.scrapper.data.Content;
import com.wedimob.scrapper.data.ContentStore;
import com.wedimob.scrapper.parser.HTMLParser;
import com.wedimob.scrapper.parser.Parser;
import com.wedimob.scrapper.parser.RawParser;

/**
 * concurrent sources reader.
 * 
 * @author melouadi
 * 
 */
public class ContentReader {

	/**
	 * basic factory.
	 * 
	 * @return reader
	 */
	public static ContentReader create() {
		return new ContentReader();
	}

	/**
	 * default of concurrent connection.
	 * 
	 */
	private static final int DEFAULT_NB_CONNEXION = 10;

	/**
	 * nb of concurrent connection.
	 * 
	 */
	private int nbConcurrentAccess = DEFAULT_NB_CONNEXION;

	/**
	 * default connection timeout.
	 * 
	 */
	private static final int DEFAULT_TIMEOUT = 60000;

	/**
	 * connexion timeout.
	 */
	private int timeout = DEFAULT_TIMEOUT;

	/**
	 * default nb of retry.
	 * 
	 */
	private static final int DEFAULT_MAX_RETRY = 3;

	/**
	 * retry delay.
	 * 
	 */
	public static final int RETRY_DELAY = 20;
	
	/**
	 * nb of retry on failure.
	 * 
	 */
	private int maxRetry = DEFAULT_MAX_RETRY;

	/**
	 * defaut connexion strategy using JSoup.
	 * 
	 */
	private static ConnectionStrategy defaultStrategy = JSoupConnectionStrategy
			.create();

	/**
	 * defaut connexion strategy using JSoup.
	 * 
	 */
	private static Parser<?> defaultParser;

	static {
		defaultParser = new HTMLParser();
		defaultParser.setSuccessor(new RawParser());
	}

	/**
	 * Retrieve actual nbConcurrentAccess.
	 * 
	 * @return nbConcurrentAccess
	 */
	public final int getNbConcurrentAccess() {
		return nbConcurrentAccess;
	}

	/**
	 * define nbConcurrentAccess.
	 * 
	 * @param aNbConcurrentAccess
	 *            nbConcurrentAccess
	 */
	public final void setNbConcurrentAccess(final int aNbConcurrentAccess) {
		this.nbConcurrentAccess = aNbConcurrentAccess;
	}

	/**
	 * Retrieve actual timeout.
	 * 
	 * @return timeout
	 */
	public final int getTimeout() {
		return timeout;
	}

	/**
	 * define timeout.
	 * 
	 * @param aTimeout
	 *            timeout
	 */
	public final void setTimeout(final int aTimeout) {
		this.timeout = aTimeout;
	}

	/**
	 * Retrieve actual maxRetry.
	 * 
	 * @return maxRetry
	 */
	public final int getMaxRetry() {
		return maxRetry;
	}

	/**
	 * define maxRetry.
	 * 
	 * @param aMaxRetry
	 *            maxRetry
	 */
	public final void setMaxRetry(final int aMaxRetry) {
		this.maxRetry = aMaxRetry;
	}

	/**
	 * retrieve content of sources using jsoup.
	 * 
	 * @param sources
	 *            as uri
	 * @return store
	 */
	public final ContentStore get(final String... sources) {
		return get(defaultParser, defaultStrategy, sources);
	}

	/**
	 * retrieve content of sources using jsoup.
	 * 
	 * @param sources
	 *            as uri
	 * @param strategy
	 *            connexion
	 * @return store
	 */
	public final ContentStore get(final ConnectionStrategy strategy,
			final String... sources) {
		return get(defaultParser, strategy, sources);
	}

	/**
	 * retrieve content of sources using connexion strategy.
	 * 
	 * @param strategy
	 *            connexion
	 * @param sources
	 *            as url
	 * @param parser
	 *            chain of parser
	 * @return store
	 */
	public final ContentStore get(final Parser<?> parser,
			final ConnectionStrategy strategy, final String... sources) {

		ContentStore store = ContentStore.create(parser);
		if (sources == null || sources.length == 0) {
			return store;
		}

		Set<URI> uris = new HashSet<URI>();
		for (int i = 0; i < sources.length; i++) {
			uris.add(URI.create(sources[i]));
		}

		ExecutorService executor = Executors
				.newFixedThreadPool(nbConcurrentAccess);
		List<Future<Content>> list = new ArrayList<Future<Content>>();
		for (URI uri : uris) {
			Callable<Content> worker = null;
			worker = new DocumentResponse(strategy, this, uri);
			Future<Content> submit = executor.submit(worker);
			list.add(submit);
		}

		for (Future<Content> future : list) {
			try {
				store.addContent(future.get());
			} catch (Exception e) {
				e.printStackTrace();
				throw new RuntimeException(e);
			}
		}
		executor.shutdown();
		return store;
	}
}
